function PetriNet() {
	this._id = 0;
	this.objects = {
		place: [],
		transition: [],
		connection: [],
		token: [],
	}
	this.init();
}

void function() {
var fn = PetriNet.prototype;
fn.init = function() {
	var self = this, objects = self.objects;
	var fields = ['place', 'transition', 'connection', 'token'];
	fields.forEach(function(field) {
		self[field] = {
			create: function() {
				var _id = self._id++, obj = new self[field].node(_id);
				objects[field].push(obj);
				return obj;
			},
			get: function(name) {
				return objects[field].find(function(n) {
					return n.name == name;
				});
			},
			remove: function(name) {
				var node = self[field].get(name);
				if (!node) {
					return;
				}
				node.remove();
				return node;
			},
			rename: function(old_name, new_name) {
				var node = self[field].get(old_name),
					n_node = self[field].get(new_name);

				if (!node || n_node) {
					return;
				}

				node.name = new_name;
				return node;
			}
		}
	})

	//place
	self.place.node = function(_id) {
		this.name = 'p_' + _id;
		this.type = 'place';
	}
	self.place.node.prototype.remove = function() {
		var i = objects.place.indexOf(this), node = this;
		if (i != -1) {
			objects.place.splice(i, 1);
		}
		objects.connection.filter(function(c) {
			return c.from == node || c.to == node;
		}).forEach(function(c) {
			c.remove();
		})
		objects.token.filter(function(t) {
			return t.place == node;
		}).forEach(function(t) {
			t.remove();
		})
		this.isRemoved = true;
		return this;
	}
	self.place.node.prototype.selfTokens = function() {
		var p = this;
		return objects.token.filter(function(t) {
			return t.place == p;
		})
	}
	self.place.node.prototype.toState = function() {
		var tokens = this.selfTokens();
		return [this.name, tokens.length];
	}
	self.place.node.prototype.toJSON = function() {
		return {
			name: this.name
		}
	}

	self.place.load = function(p, original_obj) {
		var obj = self.place.create();
		obj.name = p.name;
		return obj;
	}

	// transition
	self.transition.node = function(_id) {
		this.name = 'tr_' + _id;
		this.type = 'transition';
	}
	self.transition.node.prototype.remove = function() {
		var i = objects.transition.indexOf(this), node = this;
		if (i != -1) {
			objects.transition.splice(i, 1);
		}
		objects.connection.filter(function(c) {
			return c.from == node || c.to == node;
		}).forEach(function(c) {
			c.remove();
		})
		this.isRemoved = true;
		return this;
	}
	self.transition.node.prototype.isEnable = function() {
		var tr = this, connections = self.connection.filter('to', tr);
		for (var i = connections.length; i--;) {
			if (!connections[i].isEatable()) {
				return false;
			}
		}
		return !!connections.length;
	}
	self.transition.node.prototype.eatToken = function() {
		var tr = this, connections = self.connection.filter('to', tr);
		connections.forEach(function(c) {
			c.eatToken();
		})
		return tr;
	}
	self.transition.node.prototype.addToken = function() {
		var tr = this, connections = self.connection.filter('from', tr);
		connections.forEach(function(c) {
			c.addToken();
		})
		return tr;
	}
	self.transition.node.prototype.toJSON = function() {
		return {
			name: this.name
		}
	}

	self.transition.load = function(tr, original_obj) {
		var obj = self.transition.create();
		obj.name = tr.name;
		return obj;
	}


	// connection
	self.connection.filter = function(field, value) {
		return objects.connection.filter(function(c) {
			return c[field] == value;
		})
	}
	self.connection.getLine = function(from, to) {
		return objects.connection.find(function(c) {
			return c.from == from && c.to == to;
		})
	}
	self.connection.node = function(_id) {
		this.name = 'c_' + _id;
		this.type = 'connection';
		this.count = 0;
		this.from = this.to = null;
	}
	self.connection.node.prototype.remove = function() {
		var i = objects.connection.indexOf(this);
		if (i != -1) {
			objects.connection.splice(i, 1);
		}
		this.isRemoved = true;
		return this;
	}
	self.connection.node.prototype.connect = function(from, to) {
		if (from.type == to.type) {
			throw 'connection between same type';
		}
		var c = self.connection.getLine(from, to);
		if (c) {
			c.count++;
			this.remove();
		} else {
			this.from = from;
			this.to = to;
			this.count = 1;
		}
		return this;
	}
	self.connection.node.prototype.isEatable = function() {
		var c = this, p = this.from;
		if (!p || p.type != 'place') {
			return false;
		}
		return p.selfTokens().length >= c.count;
	}
	self.connection.node.prototype.isAddable = function() {
		return  this.to.type == 'place';
	}
	self.connection.node.prototype.eatToken = function(from, to) {
		var c = this, p = this.from;
		if (!c.isEatable()) {
			// throw 'illegal operation: connection.eatToken - ' + c.name;
			return c;
		}
		p.selfTokens().slice(0, c.count).forEach(function(tk) {
			tk.remove();
		})
		return c;
	}
	self.connection.node.prototype.addToken = function(from, to) {
		var c = this, p = this.to;
		if (!c.isAddable()) {
			// throw 'illegal operation: connection.addToken - ' + c.name;
			return c;
		}
		for (var i = 0; i < c.count; i++) {
			var obj = self.token.create();
			obj.setIn(p);	
		}
		return c;
	}
	self.connection.node.prototype.toJSON = function() {
		return {
			name: this.name,
			count: this.count,
			from: {
				type: this.from ? this.from.type : null,
				name: this.from ? this.from.name : null
			},
			to: {
				type: this.to ? this.to.type : null,
				name: this.to ? this.to.name : null
			},
		}
	}

	self.connection.load = function(c, original_obj) {
		var obj = self.connection.create();
		obj.name = c.name;
		obj.count = c.count;
		if (c.from.type) {
			obj.from = self[c.from.type].get(c.from.name);
		}
		if (c.to.type) {
			obj.to = self[c.to.type].get(c.to.name);
		}
		return obj;
	}

	// token
	
	self.token.node = function(_id) {
		this.name = 'tk_' + _id;
		this.type = 'token';
		this.place = null;
	}
	self.token.node.prototype.remove = function() {
		var i = objects.token.indexOf(this);
		if (i != -1) {
			objects.token.splice(i, 1);
		}
		this.isRemoved = true;
		return this;
	}
	self.token.node.prototype.setIn = function(place) {
		this.place = place;
		return this;
	}

	self.token.node.prototype.toJSON = function() {
		return {
			name: this.name,
			place: this.place ? this.place.name : null
		}
	}

	self.token.load = function(tk, original_obj) {
		var obj = self.token.create();
		obj.name = tk.name;
		if (tk.place) {
			obj.place = self.place.get(tk.place);
		}
		return obj;
	}

	return self;
}

PetriNet.parse = function(json) {
	var new_petri = new PetriNet();
	var obj = typeof json == 'string' ? JSON.parse(json) : json;
	obj.place.forEach(function(p) {
		new_petri.place.load(p, obj);
	})
	obj.transition.forEach(function(tr) {
		new_petri.transition.load(tr, obj);
	})
	obj.connection.forEach(function(c) {
		new_petri.connection.load(c, obj);
	})
	obj.token.forEach(function(tk) {
		new_petri.token.load(tk, obj)
	})
	new_petri._id = obj._id;
	return new_petri;
}

fn.toJSON = function() {
	return {
		_id: this._id,
		place: this.objects.place.map(function(p) {
			return p.toJSON();
		}),
		transition: this.objects.transition.map(function(tr) {
			return tr.toJSON();
		}),
		connection: this.objects.connection.map(function(c) {
			return c.toJSON();
		}),
		token: this.objects.token.map(function(tk) {
			return tk.toJSON();
		}),
	}
}

fn.clone = function() {
	var self = this, obj = JSON.stringify(self.toJSON());
	return PetriNet.parse(obj);
}

fn.getEnableTransitions = function() {
	var self = this;
	return self.objects.transition.filter(function(tr) {
		return tr.isEnable();
	})
}

fn.isConflic = function(tr1, tr2) {
	var self = this;
	if (tr1 == tr2) {
		return true;
	}
	var froms1 = self.connection.filter('to', tr1).map(function(c) {return c.from;}),
		froms2 = self.connection.filter('to', tr2).map(function(c) {return c.from;});
	for (var i = froms1.length; i--; ) {
		if (froms2.indexOf(froms1[i]) != -1) {
			return true;
		}
	}
	return false;
}

fn.isEnableTransition = function(tr) {
	return tr.isEnable();
}

fn.state = function() {
	var self = this;
	return JSON.stringify(self.objects.place.map(function(t) {
		return t.toState();
	}));
}

}();
